#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
#include <netdb.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include "open.h"
#include "sbuf.h"

#define MAXLINE         1024
#define NTHREADS        4
#define SBUFSIZE        16

sbuf_t sbuf;            /* shared buffer of connected descriptors */
static int byte_cnt;    /* byte counter */
static sem_t mutex;     /* and the mutex that protects it */

static void init_echo_cnt(void)
{
    sem_init(&mutex, 0, 1);
    byte_cnt = 0;
}

void echo(int connfd)
{
    int i;
    size_t n;
    char buf[MAXLINE];
    static pthread_once_t once = PTHREAD_ONCE_INIT;

    pthread_once(&once, init_echo_cnt);
    while((n = read(connfd, buf, MAXLINE)) != 0)
    {
        P(&mutex);
        byte_cnt += n;
        printf("thread 0x%x received %d (%d total) bytes on fd %d\n",
            (int)pthread_self(), n, byte_cnt, connfd);
        V(&mutex);
        write(connfd, buf, n);
    }
}

void *thread(void *arg)
{
    pthread_detach(pthread_self());

    while (1) {
        int connfd = sbuf_remove(&sbuf);    /* remove connfd from buffer */
        echo(connfd);                       /* service client */
        close(connfd);
    }
}

int main(int argc, char const* argv[])
{
    int listenfd, connfd, port, clientlen, i;
    struct sockaddr_in clientaddr;
    fd_set read_set, ready_set;
    struct hostent *hp;
    char *haddrp;
    char buf[MAXLINE];
    pthread_t tid;

    if (argc != 2)
    {
        printf("usage: %s <port>\n", argv[0]);
        exit(0);
    }
    port = atoi(argv[1]);
    listenfd = open_listenfd(port);
    if (listenfd < 0)
    {
        perror("open listenfd error");
        exit(0);
    }

    sbuf_init(&sbuf, SBUFSIZE);

    FD_ZERO(&read_set);
    FD_SET(STDIN_FILENO, &read_set);
    FD_SET(listenfd, &read_set);

    for (i = 0; i < NTHREADS; i++) {
        pthread_create(&tid, NULL, thread, NULL); 
    }

    while(1)
    {
        ready_set = read_set;
        select(listenfd+1, &ready_set, NULL, NULL, NULL);

        /* read command line from stdin */
        if (FD_ISSET(STDIN_FILENO, &ready_set))
        {
            if (!fgets(buf, MAXLINE, stdin))
                exit(0);
            printf("%s", buf); 
        }

        /* echo client input */
        if (FD_ISSET(listenfd, &ready_set))
        {
            clientlen = sizeof(clientaddr);
            connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen);

            /* determine the domain name and IP address of the client */
            hp = gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr,
                sizeof(struct in_addr), AF_INET);
            haddrp = inet_ntoa(clientaddr.sin_addr);
            printf("server connected to %s (%s)\n", hp->h_name, haddrp);

            sbuf_insert(&sbuf, connfd);     /* insert connfd in buffer */
        }
    }

    return 0;
}
